using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using SubSonic.Utilities;

namespace SubSonic.Enterprise
{
    public abstract class EnterpriseController<R, C> : MarshalByRefObject 
        where R: GenericRecord<R>, new() 
        where C: EnterpriseController<R, C>, new()
    {

        #region DB Properties/Methods

        /// <summary>
        /// Returns a default setting per data type
        /// </summary>
        /// <param name="column"></param>
        /// <returns></returns>
        protected static object GetDefaultSetting(TableSchema.TableColumn column)
        {
            return Utility.GetDefaultSetting(column);
        }

        public DbType GetDBType(string columnName)
        {
            TableSchema.TableColumn col = BaseSchema.GetColumn(columnName);
            return col.DataType;
        }

        protected static TableSchema.Table table;

        /// <summary>
        /// The base static class that holds all schema info for the table.
        /// The class must be instanced at least once to populate this table.
        /// </summary>
        protected static TableSchema.Table BaseSchema
        {
            get
            {
                if (table == null)
                //if (!IsSchemaInitialized)
                {
                    new C();
                }
                return table;
            }
            set { table = value; }
        }

        protected static bool IsSchemaInitialized
        {
            get { return (table != null && table.Columns != null && table.Columns.Count > 0); }
        }
                        
        private string _providerName;

        [HiddenForDataBinding(true)]
        public string ProviderName
        {
            get { return _providerName; }
            protected set { _providerName = value; }
        }

        public TableSchema.Table GetSchema()
        {
            return BaseSchema;
        }

        /// <summary>
        /// If this object has a logical delete column, this method will append in the required parameter to avoid returning 
        /// deleted records
        /// </summary>
        /// <param name="q"></param>
        internal static void CheckLogicalDelete(Query q)
        {
            //check the columns and see if there's a "deleted" or "isDeleted"
            q.CheckLogicalDelete();
        }

        #endregion

        #region "Loaders"
        // remotable
        public R LoadByParam(string columnName, object paramValue)
        {
            R rec = new R();

            rec.MarkOld();
            IDataReader rdr = null;
            try
            {
                rdr = new Query(rec.GetSchema()).AddWhere(columnName, paramValue).ExecuteReader();
                if (rdr.Read())
                {
                    rec = Load(rdr);
                }

                else
                {
                    //if no records, this is new still
                    rec.MarkNew();
                }
            }
            finally
            {
                if (rdr != null)
                {
                    rdr.Close();
                }
            }
            return rec;
        }

        public R LoadByKey(object keyID)
        {
            R rec = new R();
            rec.MarkOld();
            IDataReader rdr = null;
            try
            {
                Query q = new Query(rec.GetSchema()).AddWhere(rec.GetSchema().PrimaryKey.ColumnName, keyID);
                //CheckLogicalDelete(q);
                rdr = q.ExecuteReader();
                if (rdr.Read())
                {
                    rec = Load(rdr);
                }
                else
                {
                    //if no records, this is new still
                    rec.MarkNew();
                }
            }
            finally
            {
                if (rdr != null)
                {
                    rdr.Close();
                }
            }
            return rec;
        }

        /// <summary>
        /// Loads the object with the current reader's values. Assumes the reader is already moved to
        /// first position in recordset (aka has been "Read()")
        /// </summary>
        /// <param name="rdr">The RDR.</param>
        protected R Load(IDataReader rdr)
        {
            R rec = new R();

            // jeff_huntsman: we need to turn IsLoaded off while we load
            // for the OriginalValues to be setup again
            
            rec.IsLoaded = false;
            foreach (TableSchema.TableColumn col in rec.GetSchema().Columns)
            {
                try
                {
                    rec.SetColumnValue(col.ColumnName, rdr[col.ColumnName]);                    
                }
                catch
                {
                    throw new Exception("Unable to set column value for " + col.ColumnName);
                }
            }
            rec.IsLoaded = true;
            rec.IsNew = false;

            return rec;
        }

        /// <summary>
        /// Opens the IDataReader, loads the object and closes the IDataReader. Unlike AbstractList.LoadAndCloseReader,
        /// this method does not assume that reader is open and in the first position!
        /// </summary>
        /// <param name="rdr"></param>
        protected R LoadAndCloseReader(IDataReader rdr)
        {
            try
            {
                if (rdr.Read())
                {
                    return Load(rdr);
                }
            }
            catch (Exception ex)
            {
                // just rethrow
                throw ex;
            }
            finally
            {
                if (!rdr.IsClosed)
                {
                    rdr.Close();
                }
            }
            // return a blank record
            return new R();
        }
        #endregion

        #region Fetchers

        /// <summary>
        /// Returns all records for this table
        /// </summary>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchAll()
        {
            //makes sure the table schema is loaded
            //new T();

            //build the query
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for this table, ordered
        /// </summary>
        /// <returns>Generic Typed List</returns>
        /// <param name="orderBy">Column to order by</param>
        public static IDataReader FetchAll(OrderBy orderBy)
        {
            //makes sure the table schema is loaded
            //new T();

            //build the query
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);
            q.OrderBy = orderBy;

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter, ordered by the passed in orderBy
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName">Name of the column to use in parmeter statement</param>
        /// <param name="oValue">Value of the column</param>
        /// <param name="oValue">Column to order by</param>
        /// <param name="orderBy">Ordering of results</param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, object oValue, OrderBy orderBy)
        {
            //makes sure the table schema is loaded
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.OrderBy = orderBy;
            q.AddWhere(columnName, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }


        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="oValue"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, object oValue)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());

            //load up the list
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="comparison"></param>
        /// <param name="oValue"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, Comparison comparison, object oValue)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, comparison, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="comparison"></param>
        /// <param name="oValue"></param>
        /// <param name="orderBy"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, Comparison comparison, object oValue, OrderBy orderBy)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, comparison, oValue);
            q.OrderBy = orderBy;
            CheckLogicalDelete(q);
            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());

            //load up the list
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given query
        /// </summary>
        /// <returns>Generic Typed List</returns>
        /// <param name="query">Query for complex records</param>
        public static IDataReader FetchByQuery(Query query)
        {
            //makes sure the table schema is loaded
            //new T();
            CheckLogicalDelete(query);

            //load the reader
            IDataReader rdr = DataService.GetReader(query.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Uses the passed-in object as a parameter set. Does not use the created/modified fields
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static IDataReader Find(R item)
        {
            return Find(item, null);
        }

        public static IDataReader Find(R item, OrderBy orderBy)
        {
            //build the sql string and command statements at the same time
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);

            //retrieve data from database

            foreach (TableSchema.TableColumn col in BaseSchema.Columns)
            {
                string columnName;
                object columnValue;
                columnName = col.ColumnName;
                columnValue = item.GetColumnValue<object>(columnName);
                if (!Utility.IsAuditField(columnName))
                {
                    object defaultValue = String.Empty;
                    switch (col.DataType)
                    {
                        case DbType.Boolean:
                            defaultValue = false;
                            break;
                        case DbType.Currency:
                        case DbType.Decimal:
                        case DbType.Int16:
                        case DbType.Double:
                        case DbType.Int32:
                            defaultValue = 0;
                            break;
                        case DbType.Date:
                        case DbType.DateTime:
                            defaultValue = new DateTime(1900, 1, 1);
                            break;
                        case DbType.Guid:
                            defaultValue = Guid.Empty;
                            break;
                    }
                    if (columnValue != null)
                    {
                        if (!columnValue.Equals(defaultValue))
                        {
                            q.AddWhere(columnName, columnValue);
                        }
                    }
                }
            }

            if (orderBy != null)
            {
                q.OrderBy = orderBy;
            }
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        #endregion

        #region "Remote Query"
        public static RemoteQuery<R, C> Query()
        {
            R schema = new R();
            return new RemoteQuery<R, C>(schema.GetSchema());
        }
        #endregion


    }
}
